package algebra.rewriting.relational;

import java.util.ArrayList;
//import java.util.Collections;
//import java.util.Comparator;
import java.util.HashSet;
//import java.util.Iterator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import reasoner.KBReason;
//import utils.Util;
import utils.exception.CoreReasoningException;

import kb.DescriptionLogicsKB;

import entities.assertion.ConceptAssertion;
//import entities.concept.AndConcept;
import entities.concept.Concept;

import algebra.cost.PlanEnumeration;
import algebra.op.AlgebraicOP;
import algebra.op.ConstantOP;
import algebra.op.JoinOP;
import algebra.op.PrimaryOP;
import algebra.op.ProjectionOP;
import algebra.op.ScanOP;
//import algebra.op.SelectionOP;
//import algebra.plan.CachedIndex;
import algebra.query.indexing.SecondaryIndex;
import algebra.query.pd.ProjDesc;
import algebra.rewriting.RewriteRule;
//import algebra.visitors.ProjectionOPEvaluator;
import algebra.visitors.cursors.CACursor;

/**
 * @see Our IJCAI 2011 paper Rewrite Pk into S_i(C_i) 1. Si=(Di, Pdi) 2.
 *      K|=C->D_i 3. Ci is the minimal for D
 * @author j55wu
 * 
 */
public class CacheIntro extends RewriteRule {
	private Set<SecondaryIndex> sis;
	private final Concept selc;
	private final AlgebraicOP sub;
	private final DescriptionLogicsKB kb;
	private boolean tested = false;
	private static Map<AlgebraicOP, Set<Set<SecondaryIndex>>> seenCaches = new HashMap<AlgebraicOP, Set<Set<SecondaryIndex>>>();

	public CacheIntro(AlgebraicOP in, Set<SecondaryIndex> idx,
			PlanEnumeration pe) {
		this.sub = in;
		this.kb = pe.getKB();
		this.selc = pe.getQC();
		sis = new HashSet<SecondaryIndex>();
		sis.addAll(idx);
	}

	public void pushCache(AlgebraicOP in, Set<SecondaryIndex> idx) {
		Set<Set<SecondaryIndex>> vs = null;
		if (seenCaches.containsKey(in)) {
			vs = seenCaches.get(in);
		} else {
			vs = new HashSet<Set<SecondaryIndex>>();
		}
		vs.add(idx);
		seenCaches.put(in, vs);
	}

	@Override
	public boolean applicable() {
		this.tested = true;
		if (!(sub instanceof PrimaryOP)) {
			return false;
		}
		rmDuplicateSi();
		this.filterSI(sis, kb);
		if (this.sis.size() < 1) {
			return false;
		}
		if (seenCaches.containsKey(sub) && seenCaches.get(sub).contains(sis)) {
			return false;
		} else {
			pushCache(sub, sis);
		}
		return true;
	}

	private void rmDuplicateSi() {
		if (this.sub instanceof ScanOP) {
			ScanOP sc = (ScanOP) this.sub;
			if (this.sis.contains(sc.getSecondaryIndex())) {
				sis.remove(sc.getSecondaryIndex());
			}
		} else if (this.sub instanceof JoinOP) {
			JoinOP ip = (JoinOP) this.sub;
			for (AlgebraicOP op : ip.flattenedView()) {
				if (op instanceof ScanOP) {
					ScanOP sc = (ScanOP) op;
					this.sis.remove(sc.getSecondaryIndex());
				}
			}
		}
		return;
	}

	/*
	 * Condition 1 satisfied: C->Di for all qualifying Si
	 */
	private void filterSI(Set<SecondaryIndex> idx, DescriptionLogicsKB kb) {
		Set<SecondaryIndex> remove = new HashSet<SecondaryIndex>();
		for (SecondaryIndex si : idx) {
			Concept di = si.getCi();
			KBReason res = new KBReason();
			try {
				if (!res.subsumesGuarded(this.selc, di, kb)) {
					remove.add(si);
					continue;
				}
			} catch (CoreReasoningException e) {
				e.printStackTrace();
			}
		}
		this.sis.removeAll(remove);
	}

	// returns null if a rewrite fails
	@Override
	public AlgebraicOP rewrite() {
		if (!this.tested) {
			System.err
					.println("Please call method rewritable() to see if this rule is applicable.");
			return null;
		}
		Set<ScanOP> scans = new HashSet<ScanOP>();
		boolean suc = true;
		for (SecondaryIndex si : sis) {
			Concept sc = this.canScanCiOverSi(si, this.kb);
			if (sc == null) {
				suc = false;
				break;
			} else {
				ScanOP sp = new ScanOP(new ConstantOP(sc), si);
				scans.add(sp);
			}
		}
		if (suc) {
			AlgebraicOP ret = null;
			if (this.sub instanceof PrimaryOP) {
				// dump this
				ret = generateQuery(scans);
			} else {
				// make an intersection
				ret = new JoinOP(sub, generateQuery(scans));
			}
			return ret;
		}
		return null;
	}

	// optimization: can always produce more specific Ci in inner subqueries
	public AlgebraicOP generateQuery(Set<ScanOP> scans) {
		List<ScanOP> sc = new ArrayList<ScanOP>();
		sc.addAll(scans);
		// Collections.sort(sc, new Comparator<ScanOP>(){
		// @Override
		// public int compare(ScanOP s1, ScanOP s2) {
		// Concept c1 = ((ConstantOP)s1.getSubquery()).getQueryCocnept();
		// Concept c2 = ((ConstantOP)s2.getSubquery()).getQueryCocnept();
		// boolean lt = Util.subsumes(c1, c2, kb);
		// if(lt){
		// return -1;
		// }else{
		// return 1;
		// }
		// }
		// });
		AlgebraicOP ip = JoinOP.generateIntersectionOP(sc);
		// SelectionOP sp=new SelectionOP(ip, selc);
		// sp.setPlanEvalKB(kb);
		// ProjectionOP pp = new ProjectionOP(pd, sp);
		// pp.setPlanEvalKB(kb);
		// return pp;
		return ip;
	}

	/*
	 * decide if cond 3 is satisfied.
	 */
	public static void clearCachedCp(){
		useCp.clear();
		seenCaches.clear();
	}
	private static Map<SecondaryIndex, Concept> useCp = new HashMap<SecondaryIndex, Concept>();
	private Concept canScanCiOverSi(SecondaryIndex si, DescriptionLogicsKB kb) {
		if(useCp.containsKey(si)){
			return useCp.get(si);
		}
		ProjDesc pdi = si.getPdi();
		ProjectionOP pop = new ProjectionOP(pdi, new ConstantOP(this.selc), kb);
		CACursor cur = CACursor.getNewCACursor(pop);
		cur.openCursor();
		ConceptAssertion ca = cur.getNextResult();
		if (ca == null) {
			return null;
		}
		Concept cp = Concept.simplifyConjunctiveConcept(ca.getConcept());
		useCp.put(si, cp);
		return cp;
	}

}
